package com.fanx.redis.lock.spring.annotation;

import com.fanx.redis.lock.core.RedisLockable;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AopInvocationException;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * @author fanhang
 */
public class RedisLockMethodInterceptor implements MethodInterceptor {

	private final Logger log = LoggerFactory.getLogger(RedisLockMethodInterceptor.class);

	private RedisLockExpressionEvaluator expressionEvaluator = new RedisLockExpressionEvaluator();

	private RedisLockable redisLockable;

	public RedisLockMethodInterceptor(RedisLockable redisLockable) {
		this.redisLockable = redisLockable;
	}

	@Override
	public Object invoke(MethodInvocation invocation) throws Throwable {
		invocation.getMethod();
		RedisLock redisLock = invocation.getMethod().getAnnotation(RedisLock.class);
		Assert.notNull(redisLock, "@RedisLock not found");
		String lockName = redisLock.lockName();
		Assert.hasLength(lockName, "lockName must not be empty");
		String paramKey = null;
		if (StringUtils.hasLength(redisLock.lockKey())) {
			paramKey = getLockParamKey(redisLock.lockKey(), invocation);
		}
		String redisKey = paramKey == null ? lockName : lockName + ":" + paramKey;
		int expireSeconds = redisLock.expireSeconds();
		String uuid = UUID.randomUUID().toString();
		if (redisLockable.lock(redisKey, uuid, expireSeconds, redisLock.retryCount(), redisLock.retryInterval())) {
			try {
				log.debug("[redis-lock fetch success, start business...");
				return invocation.proceed();
			}
			catch (Throwable throwable) {
				log.error("redis-lock business exception", throwable);
				throw throwable;
			}
			finally {
				boolean unlockResult = redisLockable.unlock(lockName, uuid);
				log.debug("redis-lock release : {}", unlockResult);
			}
		}
		else {
			throw new AopInvocationException("redis-lock fetch fail");
		}
	}

	/**
	 * 获取锁的key值
	 * @param lockKey
	 * @param invocation
	 * @return
	 */
	private String getLockParamKey(String lockKey, MethodInvocation invocation) {
		EvaluationContext evaluationContext = expressionEvaluator.createEvaluationContext(invocation.getThis(),
				invocation.getMethod(), invocation.getArguments());
		AnnotatedElementKey elementKey = new AnnotatedElementKey(invocation.getMethod(),
				invocation.getThis().getClass());
		Object key = expressionEvaluator.key(lockKey, elementKey, evaluationContext);
		if (key == null) {
			return null;
		}
		return key.toString();
	}

}
